<?php


namespace App\Commons\Utils\ModelTools;


use Illuminate\Database\Eloquent\Model;
class BaseModel extends Model
{
    use FilterQueryTrait;
    
    /**
     * deleted 字段值 1:表示删除，0:表示没有被删除
     */
    const DELETED_YES = 1;
    const DELETED_NO  = 0;
    
    /**
     * 是否模型查询中使用 deleted限制，如果表中无该字段的可以设置其为false
     * @var bool
     */
    protected $switchUseScopeNotDelete = true;
    
    /**
     * @param $query
     */
    public function scopeNotDelete($query) {
        $this->switchUseScopeNotDelete && $query->where('deleted', self::DELETED_NO);
    }
    
    /**
     * unix : unix timestamp 13 length
     *
     * @var string default:unix
     */
    protected $dateFormat = 'unix';

    /**
     * @var array 默认列表字段
     */
    protected $defaultListFields = ['*'];

    /**
     * Create a new Eloquent query builder for the model.
     *
     * @param  \Illuminate\Database\Query\Builder $query
     *
     * @return \Illuminate\Database\Eloquent\Builder|static
     */
    public function newEloquentBuilder($query)
    {
        return new BaseEloquentBuilder($query);
    }

    /**
     * @var array
     */
    protected $searchable = [];

    /**
     * sql别名字段，e.g  column1+column2 as aliasColumn
     * [
     *    'aliasColumn' => column1+column2;
     * ]
     * @var array
     */
    protected $asFieldList = [];

    /**
     * 查询字段为id以逗号包含的并列形式，e.g  key：id; value: ,1,2,3,4,
     * [
     *    'key1',
     *    'key2',
     * ]
     * @var array
     */
    protected $appositionField = [];

    /**
     * 在JSON字段中查询
     * [
     *    'field1' => [
     *          'field2' => ['field3'],//可查询field1->field2->field3
     *      ],
     *    'foo1' => [
     *          'foo2'
     *      ]
     * ]
     * @var array
     */
    protected $findInJsonField = [];
    const FIND_IN_JSON_FIELD_SEPARATOR = '|';

    /**
     * 添加精度字段类型转换 $cast => ['field1'=> 'round:2' ]
     *
     * @param string $key
     * @param mixed  $value
     *
     * @return float|mixed
     */
    protected function castAttribute($key, $value)
    {
        $type = $this->getCastType($key);
        if (stripos($type, 'round') !== false) {
            $_tempRound = explode(':', $type);

            return round($value, $_tempRound[1] ?? 2);
        }

        return parent::castAttribute($key, $value);
    }


    /**
     * Convert a DateTime to a storable string.
     *
     * @param  \DateTime|int $value
     *
     * @return string
     */
    public function fromDateTime($value)
    {
        if ($this->_isUnixDateTime()) {
            if (!is_numeric($value) && !empty($value)) {
                return $this->formatUnixTimestamp($value);
            } else {
                return $value;
            }
        }

        return empty($value)
            ? $value
            : $this->asDateTime($value)->format($this->getDateFormat());
    }

    /**
     * @param $value
     *
     * @return int
     */
    protected function formatUnixTimestamp($value): int
    {
        return (int)($this->asDateTime($value)->format('Uu') / 1000);
    }


    /**
     * Return a timestamp as DateTime object.
     *
     * @param  mixed $value
     *
     * @return \Illuminate\Support\Carbon
     */
    protected function asDateTime($value)
    {
        if ($this->_isUnixDateTime()) {
            return $value;
        }

        return parent::asDateTime($value);
    }


    /**
     * Add the casted attributes to the attributes array.
     *
     * @param  array $attributes
     * @param  array $mutatedAttributes
     *
     * @return array
     */
    protected function addCastAttributesToArray(array $attributes, array $mutatedAttributes)
    {
        foreach ($this->getCasts() as $key => $value) {
            if (!array_key_exists($key, $attributes) || in_array($key, $mutatedAttributes)) {
                continue;
            }

            // Here we will cast the attribute. Then, if the cast is a date or datetime cast
            // then we will serialize the date for the array. This will convert the dates
            // to strings based on the date format specified for these Eloquent models.
            $attributes[$key] = $this->castAttribute(
                $key, $attributes[$key]
            );

            // If the attribute cast was a date or a datetime, we will serialize the date as
            // a string. This allows the developers to customize how dates are serialized
            // into an array without affecting how they are persisted into the storage.
            if ($attributes[$key] &&
                ($value === 'date' || $value === 'datetime')) {
                if (!$this->_isUnixDateTime()) {
                    $attributes[$key] = $this->serializeDate($attributes[$key]);
                }
            }
        }

        return $attributes;
    }


    /**
     * Add the date attributes to the attributes array.
     *
     * @param  array $attributes
     *
     * @return array
     */
    protected function addDateAttributesToArray(array $attributes)
    {
        foreach ($this->getDates() as $key) {
            if (!isset($attributes[$key])) {
                continue;
            }

            if ($this->_isUnixDateTime()) {

                $attributes[$key] = $this->asDateTime($attributes[$key]);
            } else {
                $attributes[$key] = $this->serializeDate(
                    $this->asDateTime($attributes[$key])
                );
            }
        }

        return $attributes;
    }

    /**
     * @return bool
     */
    private function _isUnixDateTime(): bool
    {
        return strtolower($this->getDateFormat()) == 'unix';
    }

    /**
     * @param       $query
     * @param array $conditions
     */
    public function scopeFilterQuery($query, array $conditions) {
        foreach ($conditions as $condition) {
            if (
                !isset($condition['type']) ||
                !isset($condition['field_name']) ||
                !isset($condition['field_type']) ||
                !in_array($condition['field_name'], $this->searchable)
            ) {
                continue;
            }

            //特殊别名字段条件处理
            $isAliasColumn = array_key_exists($condition['field_name'], $this->asFieldList);
            //并列字段条件处理
            $isAppositionColumn = in_array($condition['field_name'], $this->appositionField);
            //是否在json中查找
            if (str_contains($condition['field_name'], self::FIND_IN_JSON_FIELD_SEPARATOR) && !$this->checkJsonField($condition)) {
                continue;
            }

            $this->buildFilterQuery(
                $query, $condition['type'],
                $isAliasColumn ? $this->asFieldList[$condition['field_name']] : $condition['field_name'],
                $condition['field_type'],
                $condition['value'] ?? '',
                $isAliasColumn,
                $isAppositionColumn
            );
        }
    }

    /**
     * @param $query
     *
     * @return mixed
     */
    public function scopeDefaultFields($query)
    {
        return $query->select($this->defaultListFields);
    }

    /**
     * json查询字段转换 .号转换成-> 符号 e.g, field1|field2|field3  转换成 field1->field2->field3
     * @param $condition
     * @return bool
     */
    public function checkJsonField(&$condition) {
        $tempFiledNameList = explode(self::FIND_IN_JSON_FIELD_SEPARATOR, $condition['field_name']);
        $lastLevelKey      = array_pop($tempFiledNameList);
        $jsonFiledListCopy = $this->findInJsonField;
        $isSetParentKey    = true;
        foreach ($tempFiledNameList as $fieldLevelKey) {
            if (!isset($jsonFiledListCopy[$fieldLevelKey])) {
                $isSetParentKey = false;
                break;
            } else {
                $jsonFiledListCopy = $jsonFiledListCopy[$fieldLevelKey];
            }
        }
        if ($isSetParentKey && in_array($lastLevelKey, $jsonFiledListCopy)) {
            $condition['field_name'] = str_replace(self::FIND_IN_JSON_FIELD_SEPARATOR, '->', $condition['field_name']);
            return true;
        } else {
            return false;
        }
    }
}